Source Xiong, Ng, and Hanzo 2021
Source Nakaji & Yamamoto, 2020
Optimizer qui sont les plus utilisés en Deep Learning (source analyticsvidhya a-comprehensive-guide-on-deep-learning-optimizer)
Toy example sur classification de données en chaines binaires mappées sur un label/étiquette (0011 -> 1).
import pennylane as qml
from pennylane import numpy as np
from pennylane.optimize import NesterovMomentumOptimizer
Création du simulator avec device avec 4 qubits en utilisant le simulateur lightning.qubit.
dev = qml.device("lightning.qubit", wires=4)
Création d'une couche ou bloc pour le circuit paramétrisé qui sera utilisé pour générer l'algorithme variationnel.
def layer(layer_weights):
'''
Function to build simple layer with rotations gates and cnot. Basis element of a parametrized circuit.
Args:
layer_weights: weights/angles to use for the rotations
Return:
Parametrized layer
'''
# Rotations in the 3 axes
for wire in range(4):
qml.Rot(*layer_weights[wire], wires=wire)
for wires in ([0, 1], [1, 2], [2, 3], [3, 0]):
qml.CNOT(wires)
Création de la fonction pour encoder les données classique dans un état quantique. BasisState permet d'encoder une chaine de 0s et de 1s (bitstring) dans la base de calcul:
$$
x = 0101 \ \rightarrow \ \vert \psi\rangle = \vert 0101 \rangle
$$
def statepreparation(x):
'''
Basis encoding for an arbitrary x vector
Args:
x: vector of dimension 4
Return:
Layer phi with X gates to encode the vector x
'''
qml.BasisState(x, wires=[0, 1, 2, 3])
Création du cricuit paramétrisé. Le qnode permet de mapper le circuit sur le simulateur choisi au départ. Le paramètre interface est utilisé pour calculer la backpropagation classique dans l'algorithme variationnel. Le paramètre weights permet de déterminer le nombre de couche dans le circuit paramétrisé déterminé par la fonction layer.
@qml.qnode(dev, interface="autograd")
def circuit(weights, x):
'''
Parametrized circuit with data encoding (statepreparation) and layer repetition based on the weights
Args:
weights: angles for the rotations (num layer, num qubits, num directions)
x: input vector
Return:
Expectation values measured on Pauli Z operators for the state 0
'''
# data encoding
statepreparation(x)
# ansatz
for W in weights:
layer(W)
# measure
return qml.expval(qml.PauliZ(0))
Construction du cricuit pour l'algorithme variationnel, on ajoute un terme biais pour plus de liberté dans l'apprentissage.
def variational_classifier(weights, bias, x):
'''
Build the parametrized circuit with weights, x and bias term
Args:
- weights: rotation angles
- bias: classical term to add more freedom to the VQA
- x: input vector/data
Returns:
- parametrized circuit with a bias term
'''
return circuit(weights, x) + bias
La seconde étape est de pouvoir construire les métriques et outils pour diriger l'apprentissage. Dans l'apprentissage supervisé, la fonction de coût est généralement la somme d'une fonction de perte et d'un régularisateur. Nous nous limitons à la différence au carrée entre la valeur prédite et la valeur de référence. La fonction de coût mesure la distance (erreur) entre les étiquettes cibles et les prédictions du modèle.
def square_loss(labels, predictions):
'''
Compute the cost function
Args:
- labels: Ground truth
- predictions: Predicted values
Returns:
- Mean of the square error between labels and predictions = model's error
'''
# We use a call to qml.math.stack to allow subtracting the arrays directly
return np.mean((labels - qml.math.stack(predictions)) ** 2)
Pour savoir combien d'entrées le classificateur actuel a correctement prédit, on définit également la précision, ou la proportion de prédictions qui correspondent à un ensemble d'étiquettes cibles. L'accuracy est une métrique qui se calcule comme: $$ \frac{TP+TN}{TP+TN+FP+FN} = \frac{Bonnes \ prédictions}{Toutes \ les \ prédictions} $$
Cette métrique est vivement déconseillée dans les cas ou les données sont débalancées (la classe minoritaire ayant un volume très faible comparé à la classe majoritaire.
def accuracy(labels, predictions):
'''
Compute the accuracy of the model
Args:
- labels: Ground truth
- predictions: Predicted values
Returns:
- accuracy
'''
acc = sum(abs(l - p) < 1e-5 for l, p in zip(labels, predictions))
acc = acc / len(labels)
return acc
Maintenant, il est temps de créer la fonction costqui va regrouper les parties précédentes pour calculer l'erreur des prédictions du modèle.
def cost(weights, bias, X, Y):
'''
Compute the cost of the model
Args:
- weights: rotation angles
- bias: classical term to add more freedom to the VQA
- X: input vector/data
- Y: True labels
Returns:
- Error prediction / distance
'''
predictions = [variational_classifier(weights, bias, x) for x in X]
return square_loss(Y, predictions)
Récupération des données et split entre les données et les labels.
data = np.loadtxt("data/parity.txt")
X = np.array(data[:, :-1], requires_grad=False)
Y = np.array(data[:, -1], requires_grad=False)
Y = Y * 2 - 1 # shift label from {0, 1} to {-1, 1}
for x,y in zip(X, Y):
print(f"x = {x}, y = {y}")
x = [0. 0. 0. 0.], y = -1.0 x = [0. 0. 0. 1.], y = 1.0 x = [0. 0. 1. 0.], y = 1.0 x = [0. 0. 1. 1.], y = -1.0 x = [0. 1. 0. 0.], y = 1.0 x = [0. 1. 0. 1.], y = -1.0 x = [0. 1. 1. 0.], y = -1.0 x = [0. 1. 1. 1.], y = 1.0 x = [1. 0. 0. 0.], y = 1.0 x = [1. 0. 0. 1.], y = -1.0 x = [1. 0. 1. 0.], y = -1.0 x = [1. 0. 1. 1.], y = 1.0 x = [1. 1. 0. 0.], y = -1.0 x = [1. 1. 0. 1.], y = 1.0 x = [1. 1. 1. 0.], y = 1.0 x = [1. 1. 1. 1.], y = -1.0
np.random.seed(0)
num_qubits = 4
num_layers = 2
weights_init = 0.01 * np.random.randn(num_layers, num_qubits, 3, requires_grad=True)
bias_init = np.array(0.0, requires_grad=True)
print(weights_init, bias_init)
[[[ 0.01764052 0.00400157 0.00978738] [ 0.02240893 0.01867558 -0.00977278] [ 0.00950088 -0.00151357 -0.00103219] [ 0.00410599 0.00144044 0.01454274]] [[ 0.00761038 0.00121675 0.00443863] [ 0.00333674 0.01494079 -0.00205158] [ 0.00313068 -0.00854096 -0.0255299 ] [ 0.00653619 0.00864436 -0.00742165]]] 0.0
np.shape(weights_init)
(2, 4, 3)
fig, ax = qml.draw_mpl(circuit, style="sketch", decimals=3)(weights_init, X)
fig.show()
/var/folders/__/yb0d9fls4r9gb8t6yccwhq_w0000gn/T/ipykernel_6811/3649643044.py:2: UserWarning: Matplotlib is currently using module://matplotlib_inline.backend_inline, which is a non-GUI backend, so cannot show the figure. fig.show()
fig, ax = qml.draw_mpl(variational_classifier, style="sketch", decimals=3, expansion_strategy='device')(weights_init, bias_init, X[0])
fig.show()
/Users/christophe_pere/PinQ2_py/lib/python3.9/site-packages/pennylane/drawer/draw.py:536: UserWarning: When the input to qml.draw is not a QNode, the expansion_strategy argument is ignored. warnings.warn( /var/folders/__/yb0d9fls4r9gb8t6yccwhq_w0000gn/T/ipykernel_6811/473859013.py:2: UserWarning: Matplotlib is currently using module://matplotlib_inline.backend_inline, which is a non-GUI backend, so cannot show the figure. fig.show()
opt = NesterovMomentumOptimizer(0.5)
batch_size = 5
On va exécuter l'optimiseur pour entraîner notre modèle. On utilise la fonction accuracy pour estimer la part de bonnes prédictions. Pour cela, nous calculons les sorties du classificateur variationnel et les transformons en prédictions en { - 1 , 1 } en prenant le signe de la sortie.
weights = weights_init
bias = bias_init
for it in range(25):
# Update the weights by one optimizer step
batch_index = np.random.randint(0, len(X), (batch_size,))
X_batch = X[batch_index]
Y_batch = Y[batch_index]
weights, bias, _, _ = opt.step(cost, weights, bias, X_batch, Y_batch)
# Compute accuracy
predictions = [np.sign(variational_classifier(weights, bias, x)) for x in X]
acc = accuracy(Y, predictions)
print(
"Iter: {:5d} | Cost: {:0.7f} | Accuracy: {:0.7f} ".format(
it + 1, cost(weights, bias, X, Y), acc
)
)
Iter: 1 | Cost: 3.4355534 | Accuracy: 0.5000000 Iter: 2 | Cost: 1.9287800 | Accuracy: 0.5000000 Iter: 3 | Cost: 2.0341238 | Accuracy: 0.5000000 Iter: 4 | Cost: 1.6372574 | Accuracy: 0.5000000 Iter: 5 | Cost: 1.3025395 | Accuracy: 0.6250000 Iter: 6 | Cost: 1.4555019 | Accuracy: 0.3750000 Iter: 7 | Cost: 1.4492786 | Accuracy: 0.5000000 Iter: 8 | Cost: 0.6510286 | Accuracy: 0.8750000 Iter: 9 | Cost: 0.0566074 | Accuracy: 1.0000000 Iter: 10 | Cost: 0.0053045 | Accuracy: 1.0000000 Iter: 11 | Cost: 0.0809483 | Accuracy: 1.0000000 Iter: 12 | Cost: 0.1115426 | Accuracy: 1.0000000 Iter: 13 | Cost: 0.1460257 | Accuracy: 1.0000000 Iter: 14 | Cost: 0.0877037 | Accuracy: 1.0000000 Iter: 15 | Cost: 0.0361311 | Accuracy: 1.0000000 Iter: 16 | Cost: 0.0040937 | Accuracy: 1.0000000 Iter: 17 | Cost: 0.0004899 | Accuracy: 1.0000000 Iter: 18 | Cost: 0.0005290 | Accuracy: 1.0000000 Iter: 19 | Cost: 0.0024304 | Accuracy: 1.0000000 Iter: 20 | Cost: 0.0062137 | Accuracy: 1.0000000 Iter: 21 | Cost: 0.0088864 | Accuracy: 1.0000000 Iter: 22 | Cost: 0.0201912 | Accuracy: 1.0000000 Iter: 23 | Cost: 0.0060335 | Accuracy: 1.0000000 Iter: 24 | Cost: 0.0036153 | Accuracy: 1.0000000 Iter: 25 | Cost: 0.0012741 | Accuracy: 1.0000000
Comme nous pouvons le voir, le classificateur variationnel a appris à classer correctement toutes les chaînes de bits de l'ensemble d'apprentissage.
Mais contrairement à l'optimisation, dans l'apprentissage automatique, l'objectif est de généraliser des données limitées à des exemples non vus. Même si le circuit quantique variationnel était parfaitement optimisé en termes de coût, il pourrait ne pas se généraliser, un phénomène connu sous le nom d'overfitting. L'art de l'apprentissage automatique (quantique) consiste à créer des modèles et des procédures d'apprentissage qui tendent à trouver de "bons" minima, ou qui conduisent à des modèles qui se généralisent bien.
data = np.loadtxt("data/parity_test.txt", dtype=int)
X_test = np.array(data[:, :-1])
Y_test = np.array(data[:, -1])
Y_test = Y_test * 2 - 1 # shift label from {0, 1} to {-1, 1}
predictions_test = [np.sign(variational_classifier(weights, bias, x)) for x in X_test]
for x,y,p in zip(X_test, Y_test, predictions_test):
print(f"x = {x}, y = {y}, pred={p}")
acc_test = accuracy(Y_test, predictions_test)
print("\nAccuracy on unseen data:", acc_test)
x = [0 0 0 0], y = -1, pred=-1.0 x = [0 0 1 1], y = -1, pred=-1.0 x = [1 0 1 0], y = -1, pred=-1.0 x = [1 1 1 0], y = 1, pred=1.0 x = [1 1 0 0], y = -1, pred=-1.0 x = [1 1 0 1], y = 1, pred=1.0 Accuracy on unseen data: 1.0
Le circuit quantique a également appris à prédire parfaitement tous les exemples non vus ! C'est en fait remarquable, car la stratégie d'encodage crée des états quantiques à partir des données qui n'ont aucun chevauchement - et donc les états créés à partir de l'ensemble de test n'ont aucun chevauchement avec les états créés à partir de l'ensemble d'apprentissage. Le circuit variationnel pourrait apprendre de nombreuses relations fonctionnelles à partir de ce type de représentation, mais le classificateur choisit d'étiqueter les chaînes de bits en fonction de notre vérité de base, la fonction de parité.
Préparons un simulateur avec 2 qubits pour les deux labels.
dev = qml.device("lightning.qubit", wires=2)
La préparation de l'état n'est pas aussi simple que lorsque nous représentons une chaîne de bits par un état de base. Chaque entrée x doit être traduite en un ensemble d'angles qui peuvent être introduits dans une petite routine de préparation d'état. Pour simplifier un peu les choses, nous travaillerons avec des données provenant du sous-espace positif, de sorte que nous pourrons ignorer les signes (ce qui nécessiterait une autre cascade de rotations autour de l'axe Z).
def get_angles(x):
'''
Transform data as angles
Args:
- x: input vector of iris data
Return:
- Angle encoding data
'''
beta0 = 2 * np.arcsin(np.sqrt(x[1] ** 2) / np.sqrt(x[0] ** 2 + x[1] ** 2 + 1e-12))
beta1 = 2 * np.arcsin(np.sqrt(x[3] ** 2) / np.sqrt(x[2] ** 2 + x[3] ** 2 + 1e-12))
beta2 = 2 * np.arcsin(
np.sqrt(x[2] ** 2 + x[3] ** 2)
/ np.sqrt(x[0] ** 2 + x[1] ** 2 + x[2] ** 2 + x[3] ** 2)
)
return np.array([beta2, -beta1 / 2, beta1 / 2, -beta0 / 2, beta0 / 2])
def statepreparation(a):
'''
Prepare the state according to the corresponding get_angles transformation.
Args:
- a: angles from get_angles transformation
Return:
- Parametrized circuit with the corresponding angles
'''
qml.RY(a[0], wires=0)
qml.CNOT(wires=[0, 1])
qml.RY(a[1], wires=1)
qml.CNOT(wires=[0, 1])
qml.RY(a[2], wires=1)
qml.PauliX(wires=0)
qml.CNOT(wires=[0, 1])
qml.RY(a[3], wires=1)
qml.CNOT(wires=[0, 1])
qml.RY(a[4], wires=1)
qml.PauliX(wires=0)
Test
x = np.array([0.53896774, 0.79503606, 0.27826503, 0.0], requires_grad=False)
ang = get_angles(x)
@qml.qnode(dev, interface="autograd")
def test(angles):
statepreparation(angles)
return qml.state()
state = test(ang)
print("x : ", x)
print("angles : ", ang)
print("amplitude vector: ", np.real(state))
x : [0.53896774 0.79503606 0.27826503 0. ] angles : [ 0.56397465 -0. 0. -0.97504604 0.97504604] amplitude vector: [ 5.38967743e-01 0.00000000e+00 0.00000000e+00 0.00000000e+00 7.95036065e-01 0.00000000e+00 0.00000000e+00 0.00000000e+00 2.78265032e-01 0.00000000e+00 0.00000000e+00 0.00000000e+00 -2.77555756e-17 0.00000000e+00 0.00000000e+00 0.00000000e+00]
def layer(layer_weights):
for wire in range(2):
qml.Rot(*layer_weights[wire], wires=wire)
qml.CNOT(wires=[0, 1])
@qml.qnode(dev, interface="autograd")
def circuit(weights, angles):
statepreparation(angles)
for W in weights:
layer(W)
return qml.expval(qml.PauliZ(0))
def variational_classifier(weights, bias, angles):
return circuit(weights, angles) + bias
def cost(weights, bias, X, Y):
# Transpose the batch of input data in order to make the indexing
# in state_preparation work
predictions = variational_classifier(weights, bias, X.T)
return square_loss(Y, predictions)
data = np.loadtxt("data/iris_classes1and2_scaled.txt")
X = data[:, 0:2]
print("First X sample (original) :", X[0])
# pad the vectors to size 2^2 with constant values
padding = 0.3 * np.ones((len(X), 1))
X_pad = np.c_[np.c_[X, padding], np.zeros((len(X), 1))]
print("First X sample (padded) :", X_pad[0])
# normalize each input
normalization = np.sqrt(np.sum(X_pad ** 2, -1))
X_norm = (X_pad.T / normalization).T
print("First X sample (normalized):", X_norm[0])
# angles for state preparation are new features
features = np.array([get_angles(x) for x in X_norm], requires_grad=False)
print("First features sample :", features[0])
Y = data[:, -1]
First X sample (original) : [0.4 0.75] First X sample (padded) : [0.4 0.75 0.3 0. ] First X sample (normalized): [0.44376016 0.83205029 0.33282012 0. ] First features sample : [ 0.67858523 -0. 0. -1.080839 1.080839 ]
import matplotlib.pyplot as plt
plt.figure()
plt.scatter(X[:, 0][Y == 1], X[:, 1][Y == 1], c="b", marker="o", edgecolors="k")
plt.scatter(X[:, 0][Y == -1], X[:, 1][Y == -1], c="r", marker="o", edgecolors="k")
plt.title("Original data")
plt.savefig('original_data.png')
plt.show()
plt.figure()
dim1 = 0
dim2 = 1
plt.scatter(
X_norm[:, dim1][Y == 1], X_norm[:, dim2][Y == 1], c="b", marker="o", edgecolors="k"
)
plt.scatter(
X_norm[:, dim1][Y == -1], X_norm[:, dim2][Y == -1], c="r", marker="o", edgecolors="k"
)
plt.title("Padded and normalised data (dims {} and {})".format(dim1, dim2))
plt.savefig('normalised_data.png')
plt.show()
plt.figure()
dim1 = 0
dim2 = 3
plt.scatter(
features[:, dim1][Y == 1], features[:, dim2][Y == 1], c="b", marker="o", edgecolors="k"
)
plt.scatter(
features[:, dim1][Y == -1], features[:, dim2][Y == -1], c="r", marker="o", edgecolors="k"
)
plt.title("Feature vectors (dims {} and {})".format(dim1, dim2))
plt.savefig('feature_vectors.png')
plt.show()
fig, ax = qml.draw_mpl(statepreparation, style="sketch", decimals=3)([np.pi/2,np.pi/2,np.pi/2,np.pi/2,np.pi/2])
fig.savefig('variationao_circuit.png')
fig.show()
/var/folders/__/yb0d9fls4r9gb8t6yccwhq_w0000gn/T/ipykernel_6811/1669354998.py:3: UserWarning: Matplotlib is currently using module://matplotlib_inline.backend_inline, which is a non-GUI backend, so cannot show the figure. fig.show()
np.random.seed(0)
num_data = len(Y)
num_train = int(0.75 * num_data)
index = np.random.permutation(range(num_data))
feats_train = features[index[:num_train]]
Y_train = Y[index[:num_train]]
feats_val = features[index[num_train:]]
Y_val = Y[index[num_train:]]
# We need these later for plotting
X_train = X[index[:num_train]]
X_val = X[index[num_train:]]
num_qubits = 2
num_layers = 6
weights_init = 0.01 * np.random.randn(num_layers, num_qubits, 3, requires_grad=True)
bias_init = np.array(0.0, requires_grad=True)
fig, ax = qml.draw_mpl(circuit, style="sketch", decimals=3)(weights_init, X)
fig.show()
/var/folders/__/yb0d9fls4r9gb8t6yccwhq_w0000gn/T/ipykernel_6811/3649643044.py:2: UserWarning: Matplotlib is currently using module://matplotlib_inline.backend_inline, which is a non-GUI backend, so cannot show the figure. fig.show()
opt = NesterovMomentumOptimizer(0.01)
batch_size = 5
# train the variational classifier
weights = weights_init
bias = bias_init
for it in range(60):
# Update the weights by one optimizer step
batch_index = np.random.randint(0, num_train, (batch_size,))
feats_train_batch = feats_train[batch_index]
Y_train_batch = Y_train[batch_index]
weights, bias, _, _ = opt.step(cost, weights, bias, feats_train_batch, Y_train_batch)
# Compute predictions on train and validation set
predictions_train = [np.sign(variational_classifier(weights, bias, f)) for f in feats_train]
predictions_val = [np.sign(variational_classifier(weights, bias, f)) for f in feats_val]
# Compute accuracy on train and validation set
acc_train = accuracy(Y_train, predictions_train)
acc_val = accuracy(Y_val, predictions_val)
print(
"Iter: {:5d} | Cost: {:0.7f} | Acc train: {:0.7f} | Acc validation: {:0.7f} "
"".format(it + 1, cost(weights, bias, features, Y), acc_train, acc_val)
)
Iter: 1 | Cost: 1.4490948 | Acc train: 0.4933333 | Acc validation: 0.5600000 Iter: 2 | Cost: 1.3312057 | Acc train: 0.4933333 | Acc validation: 0.5600000 Iter: 3 | Cost: 1.1589332 | Acc train: 0.4533333 | Acc validation: 0.5600000 Iter: 4 | Cost: 0.9806934 | Acc train: 0.4800000 | Acc validation: 0.5600000 Iter: 5 | Cost: 0.8865623 | Acc train: 0.6133333 | Acc validation: 0.7600000 Iter: 6 | Cost: 0.8580769 | Acc train: 0.6933333 | Acc validation: 0.7600000 Iter: 7 | Cost: 0.8473132 | Acc train: 0.7200000 | Acc validation: 0.6800000 Iter: 8 | Cost: 0.8177533 | Acc train: 0.7333333 | Acc validation: 0.6800000 Iter: 9 | Cost: 0.8001100 | Acc train: 0.7466667 | Acc validation: 0.6800000 Iter: 10 | Cost: 0.7681053 | Acc train: 0.8000000 | Acc validation: 0.7600000 Iter: 11 | Cost: 0.7440015 | Acc train: 0.8133333 | Acc validation: 0.9600000 Iter: 12 | Cost: 0.7583777 | Acc train: 0.6800000 | Acc validation: 0.7600000 Iter: 13 | Cost: 0.7896372 | Acc train: 0.6533333 | Acc validation: 0.7200000 Iter: 14 | Cost: 0.8397790 | Acc train: 0.6133333 | Acc validation: 0.6400000 Iter: 15 | Cost: 0.8632423 | Acc train: 0.5733333 | Acc validation: 0.6000000 Iter: 16 | Cost: 0.8693517 | Acc train: 0.5733333 | Acc validation: 0.6000000 Iter: 17 | Cost: 0.8350625 | Acc train: 0.6266667 | Acc validation: 0.6400000 Iter: 18 | Cost: 0.7966558 | Acc train: 0.6266667 | Acc validation: 0.6400000 Iter: 19 | Cost: 0.7563381 | Acc train: 0.6800000 | Acc validation: 0.7600000 Iter: 20 | Cost: 0.7315459 | Acc train: 0.7733333 | Acc validation: 0.8000000 Iter: 21 | Cost: 0.7182359 | Acc train: 0.8800000 | Acc validation: 0.9600000 Iter: 22 | Cost: 0.7339132 | Acc train: 0.8133333 | Acc validation: 0.7200000 Iter: 23 | Cost: 0.7498571 | Acc train: 0.6533333 | Acc validation: 0.6400000 Iter: 24 | Cost: 0.7593763 | Acc train: 0.6000000 | Acc validation: 0.6400000 Iter: 25 | Cost: 0.7423832 | Acc train: 0.6800000 | Acc validation: 0.6400000 Iter: 26 | Cost: 0.7361186 | Acc train: 0.7333333 | Acc validation: 0.6800000 Iter: 27 | Cost: 0.7300201 | Acc train: 0.7600000 | Acc validation: 0.6800000 Iter: 28 | Cost: 0.7360923 | Acc train: 0.6666667 | Acc validation: 0.6400000 Iter: 29 | Cost: 0.7371325 | Acc train: 0.6533333 | Acc validation: 0.6400000 Iter: 30 | Cost: 0.7234520 | Acc train: 0.7466667 | Acc validation: 0.6800000 Iter: 31 | Cost: 0.7112089 | Acc train: 0.8133333 | Acc validation: 0.7600000 Iter: 32 | Cost: 0.6923495 | Acc train: 0.9200000 | Acc validation: 0.9200000 Iter: 33 | Cost: 0.6792510 | Acc train: 0.9200000 | Acc validation: 1.0000000 Iter: 34 | Cost: 0.6756795 | Acc train: 0.9200000 | Acc validation: 0.9600000 Iter: 35 | Cost: 0.6787274 | Acc train: 0.8933333 | Acc validation: 0.8000000 Iter: 36 | Cost: 0.6938181 | Acc train: 0.7466667 | Acc validation: 0.6800000 Iter: 37 | Cost: 0.6765225 | Acc train: 0.8266667 | Acc validation: 0.8000000 Iter: 38 | Cost: 0.6708057 | Acc train: 0.8266667 | Acc validation: 0.8000000 Iter: 39 | Cost: 0.6740248 | Acc train: 0.7600000 | Acc validation: 0.7200000 Iter: 40 | Cost: 0.6835014 | Acc train: 0.6666667 | Acc validation: 0.6400000 Iter: 41 | Cost: 0.6686811 | Acc train: 0.7333333 | Acc validation: 0.7200000 Iter: 42 | Cost: 0.6217025 | Acc train: 0.8933333 | Acc validation: 0.8400000 Iter: 43 | Cost: 0.6004307 | Acc train: 0.9066667 | Acc validation: 0.9200000 Iter: 44 | Cost: 0.5910768 | Acc train: 0.9066667 | Acc validation: 0.9200000 Iter: 45 | Cost: 0.5694770 | Acc train: 0.9200000 | Acc validation: 0.9600000 Iter: 46 | Cost: 0.5588385 | Acc train: 0.9200000 | Acc validation: 0.9600000 Iter: 47 | Cost: 0.5381473 | Acc train: 0.9466667 | Acc validation: 1.0000000 Iter: 48 | Cost: 0.5205658 | Acc train: 0.9466667 | Acc validation: 1.0000000 Iter: 49 | Cost: 0.5064983 | Acc train: 0.9466667 | Acc validation: 1.0000000 Iter: 50 | Cost: 0.4969733 | Acc train: 0.9466667 | Acc validation: 1.0000000 Iter: 51 | Cost: 0.4782257 | Acc train: 0.9466667 | Acc validation: 1.0000000 Iter: 52 | Cost: 0.4536953 | Acc train: 0.9600000 | Acc validation: 1.0000000 Iter: 53 | Cost: 0.4350300 | Acc train: 1.0000000 | Acc validation: 1.0000000 Iter: 54 | Cost: 0.4272909 | Acc train: 0.9733333 | Acc validation: 0.9600000 Iter: 55 | Cost: 0.4288929 | Acc train: 0.9466667 | Acc validation: 0.9200000 Iter: 56 | Cost: 0.4261037 | Acc train: 0.9333333 | Acc validation: 0.9200000 Iter: 57 | Cost: 0.4082663 | Acc train: 0.9466667 | Acc validation: 0.9200000 Iter: 58 | Cost: 0.3698736 | Acc train: 0.9600000 | Acc validation: 0.9200000 Iter: 59 | Cost: 0.3420686 | Acc train: 1.0000000 | Acc validation: 1.0000000 Iter: 60 | Cost: 0.3253480 | Acc train: 1.0000000 | Acc validation: 1.0000000
plt.figure()
cm = plt.cm.RdBu
# make data for decision regions
xx, yy = np.meshgrid(np.linspace(0.0, 1.5, 20), np.linspace(0.0, 1.5, 20))
X_grid = [np.array([x, y]) for x, y in zip(xx.flatten(), yy.flatten())]
# preprocess grid points like data inputs above
padding = 0.3 * np.ones((len(X_grid), 1))
X_grid = np.c_[np.c_[X_grid, padding], np.zeros((len(X_grid), 1))] # pad each input
normalization = np.sqrt(np.sum(X_grid ** 2, -1))
X_grid = (X_grid.T / normalization).T # normalize each input
features_grid = np.array(
[get_angles(x) for x in X_grid]
) # angles for state preparation are new features
predictions_grid = [variational_classifier(weights, bias, f) for f in features_grid]
Z = np.reshape(predictions_grid, xx.shape)
# plot decision regions
cnt = plt.contourf(
xx, yy, Z, levels=np.arange(-1, 1.1, 0.1), cmap=cm, alpha=0.8, extend="both"
)
plt.contour(
xx, yy, Z, levels=[0.0], colors=("black",), linestyles=("--",), linewidths=(0.8,)
)
plt.colorbar(cnt, ticks=[-1, 0, 1])
# plot data
plt.scatter(
X_train[:, 0][Y_train == 1],
X_train[:, 1][Y_train == 1],
c="b",
marker="o",
edgecolors="k",
label="class 1 train",
)
plt.scatter(
X_val[:, 0][Y_val == 1],
X_val[:, 1][Y_val == 1],
c="b",
marker="^",
edgecolors="k",
label="class 1 validation",
)
plt.scatter(
X_train[:, 0][Y_train == -1],
X_train[:, 1][Y_train == -1],
c="r",
marker="o",
edgecolors="k",
label="class -1 train",
)
plt.scatter(
X_val[:, 0][Y_val == -1],
X_val[:, 1][Y_val == -1],
c="r",
marker="^",
edgecolors="k",
label="class -1 validation",
)
plt.legend()
plt.savefig('simple_classifier.png')
plt.show()